#include "QutRunner.h"

#include <QtDebug>
#include <QLibrary>

#include "QutCase.h"

QutRunner::QutRunner(QObject * parent)
    : QObject(parent)
    , max_level(QutCase::nullLevel)
    , error_level(QutCase::Assert)
    , mLibrary_k(0)
{
    _summary.start();
}

QString QutRunner::Test(QLibrary * lib)
{
    const bool alreadyLoaded = lib->isLoaded();

    if ( ! mLibrary_k++)
        add(0, TESTEVENT(QutCase::TestRun, QutCase::Data, "", ""));

    if ( ! qobject_cast<QLibrary *>(lib))
        return message(QutCase::Error, "Invalid library pointer");
    if ( ! alreadyLoaded)
        if ( ! lib->load())
            return message(QutCase::Error, "Unable to load library");

    VoidVoidFunction fp = VoidVoidFunction(lib->resolve("executeUnitTest"));
    if ( ! fp) return message(QutCase::Error,
                              "Unable to resolve executeUnitTest()");
    add(0, TESTEVENT(QutCase::Library, QutCase::Data, "", lib->fileName()));
    fp();
    if ( ! alreadyLoaded) lib->unload();
    return QString();
}

bool QutRunner::enter(QutFunction * fnp,
                       const QString & fni,
                       const QString & file,
                       const int line)
{
    if (fnp_key_hash.contains(fnp)) return false;

    FunctionKey key = grindFunctionKey();
    fnp_key_hash.insert(fnp, key);
    key_fnp_map.insert(key, fnp);
    key_fni_map.insert(key,  fni);
    key_file_map.insert(key, file);
    key_line_map.insert(key, line);
    key_state_map.insert(key, Null);
    setState(key, Active);
    add(0, TESTEVENT(QutCase::Enter, QutCase::Data, fni, line));
    return true;
}

void QutRunner::add(QutFunction * fnp,
                     QutCase event)
{
    FunctionKey key = fnp_key_hash.value(fnp);
    QString fni = key_fni_map.value(key);
    QString file = key_file_map.value(key);
    int line = key_line_map.value(key);

    QutCase::Level level = event.evaluate();
    event.setLevel(level);

    event.setFunctionInfo(fni);
    event.setFunctionFile(file);
    event.setFunctionLine(line);
    event_list.append(event);
    emit addTestCase(event);
    qDebug("add() size=%i sizeof=%i",
           event_list.size(), sizeof(event_list.first()));
    //qDebug(qPrintable(event.toString()));
    _summary.add(event);

    if (isError(level)) setState(key, Error);
    if (int(level) > int(max_level)) max_level = level;
}

void QutRunner::leave(QutFunction * fnp)
{
    FunctionKey key = fnp_key_hash.value(fnp);
    fnp_key_hash.remove(fnp);
    key_fnp_map[key] = 0;
    State state = key_state_map[key];
    if (Active == state) setState(key, Complete);
    QString fni = key_fni_map[key];
    add(0, TESTEVENT(QutCase::Leave, QutCase::Data,
                     fni, key_state_map[key]));
}

void QutRunner::finish(void)
{
    _summary.finish();
}

int QutRunner::eventCount(void) const
{
    qDebug("eventCount() = %i", event_list.size());
    return event_list.size();
}

QutCase QutRunner::eventAt(const int index) const
{
    return (0 <= index && index < event_list.size())
            ? event_list.at(index)
            : QutCase();
}

QutCase::Level QutRunner::maxLevel(void) const
{
    return max_level;
}

QutCase::List QutRunner::eventList(void) const
{
    return event_list;
}

QutSummary QutRunner::summary(void) const
{
    return _summary;
}

QutRunner::FunctionKey QutRunner::grindFunctionKey(void)
{
    FunctionKey key = QDateTime::currentMSecsSinceEpoch();
    while (key_fnp_map.contains(key)) ++key;
    return key;
}

void QutRunner::setState(const FunctionKey key,
                            const State state)
{
    key_state_map[key] = state;
}

bool QutRunner::isError(const QutCase::Level level) const
{
    return level >= error_level;
}

QString QutRunner::message(const QutCase::Level level, const QString & message)
{
    add(0, TESTEVENT(level, QutCase::Data, "", message));
    return message;
}
